#include <audio/audio.hpp>

#include <efi/device_path.hpp>
#include <efi/image_loader.hpp>
#include <efi/protocol.hpp>
#include <efi/rt.hpp>

static audio::AudioIo *io = nullptr;
static uint8_t outputIndex = 0;

using namespace efi;

namespace audio {

uint8_t volume;
audio::SampleFrequency samplesPerSecond;
audio::SampleBits bitsPerSample;
uint8_t channels;

void loadAudioDriver() {
  auto a = DevicePath::getRootFsDevicePath();
  auto b = DevicePath::fromString(u"efi/modules/AudioDxe.efi", false);
  auto c = DevicePath::append(a, b, false);

  auto handle = ImageLoader::load(c);
  auto exitInfo = ImageLoader::start(handle);

  if (exitInfo.status == EFI_SUCCESS) {
    uint64_t handleCount;
    efi::Handle *handles;

    auto bs = getSystemTable()->bootServices;

    bs->locateHandleBuffer(
        efi::Protocol::SearchType::AllHandles,
        nullptr,
        nullptr,
        &handleCount,
        &handles
    );

    for (uint64_t i = 0; i < handleCount; ++i) {
      bs->connectController(handles[i], nullptr, nullptr, true);
    }

    print(u"loaded AudioDxe\r\n");
  } else {
    print(u"unable to load AudioDxe\r\n");
    exit(EFI_LOAD_ERROR);
  }
}

void selectDefaultDevice() {
  uint64_t handleCount;
  efi::Handle *handleList;
  uint64_t portCount;
  AudioIoPort *portList;

  Protocol::locateHandleBuffer(
      EFI_AUDIO_IO_PROTOCOL_GUID,
      efi::Protocol::SearchType::ByProtocol,
      nullptr,
      &handleCount,
      &handleList
  );

  if (handleCount == 0) {
    loadAudioDriver();

    Protocol::locateHandleBuffer(
        EFI_AUDIO_IO_PROTOCOL_GUID,
        efi::Protocol::SearchType::ByProtocol,
        nullptr,
        &handleCount,
        &handleList
    );

    if (handleCount == 0) {
      print(u"unable to get audio driver handle\r\n");
      exit(EFI_LOAD_ERROR);
    }
  }

  Protocol::handleProtocol(
      EFI_AUDIO_IO_PROTOCOL_GUID,
      (void **)&io,
      handleList[0]
  );

  io->getOutputs(io, &portList, &portCount);

  for (uint64_t i = 0; i < portCount; ++i) {
    if (portList[i].type == AudioIoType::Output
        && portList[i].device == AudioDeviceEnum::Headphones) {
      outputIndex = i;
      break;
    }
  }
}

void play(Audio audioData) {
  if (!io) {
    print(u"audio: invalid AudioIo instance\r\n");
    return;
  }

  if (!audioData.samples) {
    print(u"audio: invaild audio\r\n");
    return;
  }

  printf(
      u"audio info: %dbits, %dHz, %d channel(s), %d bytes in total\r\n",
      audioData.format.bitsPerSample,
      audioData.format.samplesPerSec,
      audioData.format.channelCount,
      audioData.size
  );

  switch (audioData.format.bitsPerSample) {
  case 8:
    bitsPerSample = Bits8;
    break;

  case 16:
    bitsPerSample = Bits16;
    break;

  case 20:
    bitsPerSample = Bits20;
    break;

  case 24:
    bitsPerSample = Bits24;
    break;

  case 32:
    bitsPerSample = Bits32;
    break;

  default:
    print(u"invaild bitdepth\r\n");
    while (1)
      ;
  }

  switch (audioData.format.samplesPerSec) {
  case 8000:
    samplesPerSecond = Freq8kHz;
    break;

  case 11025:
    samplesPerSecond = Freq11kHz;
    break;

  case 16000:
    samplesPerSecond = Freq16kHz;
    break;

  case 22050:
    samplesPerSecond = Freq22kHz;
    break;

  case 32000:
    samplesPerSecond = Freq32kHz;
    break;

  case 44100:
    samplesPerSecond = Freq44kHz;
    break;

  case 48000:
    samplesPerSecond = Freq48kHz;
    break;

  case 88200:
    samplesPerSecond = Freq88kHz;
    break;

  case 96000:
    samplesPerSecond = Freq96kHz;
    break;

  case 192000:
    samplesPerSecond = Freq192kHz;
    break;

  default:
    print(u"invaild freq\r\n");
  }

  channels = audioData.format.channelCount;

  print(u"play\r\n");

  io->setupPlayback(
      io,
      outputIndex,
      volume,
      samplesPerSecond,
      bitsPerSample,
      channels
  );
  io->startPlayback(io, audioData.samples, audioData.size, 0);
}

void updatePlaybackConfig() {
  if (!io) {
    print(u"audio: invalid AudioIo instance\r\n");
    return;
  }

  io->setupPlayback(
      io,
      outputIndex,
      volume,
      samplesPerSecond,
      bitsPerSample,
      channels
  );
}

void play(void *data, size_t length, uint64_t position) {
  if (!io) {
    print(u"audio: invalid AudioIo instance\r\n");
    return;
  }

  io->startPlayback(io, data, length, position);
}

void playAsync(void *data, size_t length, uint64_t position) {
  if (!io) {
    print(u"audio: invalid AudioIo instance\r\n");
    return;
  }

  io->startPlaybackAsync(io, data, length, position, nullptr, nullptr);
}

void stop() {
  if (!io) {
    print(u"audio: invalid AudioIo instance\r\n");
    return;
  }

  io->stopPlayback(io);
}

} // namespace audio